﻿namespace MapleRock.P2PEmulator.Test.Experiments
{
  using System;
  using System.Collections.Generic;
  using System.Diagnostics;
  using System.IO;
  using System.Linq;
  using System.Text;

  using Core;
  using Core.Events;

  using NUnit.Framework;

  [TestFixture]
  internal sealed class F1
  {
    [TestCase]
    public void Experiment_F1()
    {
      File.Delete("f1_worst.csv");
      File.Delete("f1_best.csv");
      File.Delete("f1_average.csv");

      var pns = new int[] { 200, 1000, 10000, 100000, 1000000 };

      foreach (var pn in pns)
      {
        for (var i = 0; i < 5; ++i)
        {
          Trace.Write(string.Format("pn = {0}, N{1}, ", pn, i));

          var worst = 0.0;
          var best = 0.0;
          var average = 0.0;

          var startTime = DateTime.Now;
          F1.Experiment_F1_PerformRun(pn, out worst, out best, out average);
          var endTime = DateTime.Now;

          Trace.WriteLine(endTime - startTime);

          F1.Experiment_F1_SaveRunResults(pn, worst, best, average);

          GC.Collect();
        }
      }
    }

    private static void Experiment_F1_PerformRun(
      int peersNumber,
      out double worst,
      out double best,
      out double average)
    {
      var d = 3;
      var a_lat = 300.0;
      var a_lon = 300.0;
      var cn = 10;
      var c_min = 1;
      var c_max = 5;
      var t_t = Convert.ToInt32(TimeSpan.FromMinutes(1).TotalMilliseconds);

      var network = Utils.CreateNetwork(
        d, 
        a_lat, 
        a_lon, 
        cn, 
        c_min, 
        c_max, 
        peersNumber, 
        PeerBehaviour.DoesNothing);

      var fromPeers = new uint[50];
      var toPeers = new uint[50];
      Experiment_F1_PerformRun_SelectFromAndToPeers(network, ref fromPeers, ref toPeers);

      Experiment_F1_ScheduleSearchRequests(network, fromPeers, toPeers);

      network.PrepareRun();

      var deliveryTimes = new List<double>();
      var messagesDelivered = 0;
      for (;;)
      {
        if (network.State.TimePassedMs > 10000)
        {
          break;
        }

        var wasDelivered = messagesDelivered;
        Experiment_F1_PerformRun_CountDeliveredMessages(network, toPeers, ref messagesDelivered);
        if (wasDelivered != messagesDelivered)
        {
          deliveryTimes.Add(network.State.TimePassedMs);
        }

        if (50 == messagesDelivered)
        {
          break;
        }

        network.Go(1);
      }

      worst = deliveryTimes.Max();
      best = deliveryTimes.Min();
      average = (worst - best) / 2;

      network.SaveRunResults();
    }

    private static void Experiment_F1_PerformRun_SelectFromAndToPeers(
      Network network,
      ref uint[] fromPeers,
      ref uint[] toPeers)
    {
      Debug.Assert(fromPeers.Length == 50, "fromPeers.Length == 50");
      Debug.Assert(toPeers.Length == 50, "toPeers.Length == 50");

      var r = new Random();
      for (var i = 0; i < 50;)
      {
        uint nextId = Convert.ToUInt32(r.Next(1, network.State.Peers.Count));
        if (fromPeers.Contains(nextId) || toPeers.Contains(nextId))
        {
          continue;
        }

        fromPeers[i] = nextId;

        for (;;)
        {
          nextId = Convert.ToUInt32(r.Next(1, network.State.Peers.Count));
          if (fromPeers.Contains(nextId) || toPeers.Contains(nextId))
          {
            continue;
          }

          break;
        }

        toPeers[i++] = nextId;
      }
    }

    private static void Experiment_F1_ScheduleSearchRequests(
      Network network,
      uint[] fromPeers,
      uint[] toPeers)
    {
      Debug.Assert(fromPeers.Length == toPeers.Length, "fromPeers.Length == toPeers.Length");
      Debug.Assert(
        fromPeers.All(iF => !toPeers.Any(iT => iT == iF)),
        "fromPeers.All(iF => !toPeers.Any(iT => iT == iF))");

      for (var i = 0; i < fromPeers.Length; ++i)
      {
        var fromPeer = network.State.Peers[fromPeers[i]];
        var toPeer = network.State.Peers[toPeers[i]];

        var request = new MessageSearchRequest(
          network.State.GenerateUniqueId(),
          fromPeer.Address,
          Guid.NewGuid());
        request.Destination = (double[])toPeer.AbsolutePosition.Clone();
        request.From = fromPeer.Id;
        request.SearchRadius = 0.01;
        request.To = fromPeer.Id;

        fromPeer.Outgoing.Enqueue(request);
      }
    }

    private static void Experiment_F1_PerformRun_CountDeliveredMessages(
      Network network,
      uint[] toPeers,
      ref int messagesDelivered)
    {
      foreach (var peer in network.State.Peers.Values)
      {
        MessageSearchRequest request = null;

        foreach (var message in peer.Incoming)
        {
          var requestMessage = message as MessageSearchRequest;
          if (requestMessage == null)
          {
            continue;
          }

          var distance = Geometry.EvaluateDistance(peer.AbsolutePosition, requestMessage.Destination);
          if (distance <= requestMessage.SearchRadius)
          {
            ++messagesDelivered;
            request = requestMessage;
            break;
          }
        }

        for (var i = 0; i < peer.Incoming.Count; ++i)
        {
          var message = peer.Incoming.Dequeue();
          if (message == request)
          {
            break;
          }

          peer.Incoming.Enqueue(message);
        }
      }
    }

    private static void Experiment_F1_SaveRunResults(int pn, double worst, double best, double average)
    {
      File.AppendAllText("f1_worst.csv", string.Format("{0};{1}\r\n", pn, worst));
      File.AppendAllText("f1_best.csv", string.Format("{0};{1}\r\n", pn, best));
      File.AppendAllText("f1_average.csv", string.Format("{0};{1}\r\n", pn, average));
    }
  }
}
